/**
 * @file dataProcess.c
 * @author your name (you@domain.com)
 * @brief 创建数据输入的接口，处理加工完数据将其放入存储中,并提供数据读取接口
 * @version 0.1
 * @date 2025-09-25
 * 
 * @copyright Copyright (c) 2025
 * 
 */
#include "dataProcess.h"
#include "dataPool.h"
#include "FreeRTOS.h"
#include "queue.h"
#include "norflash.h"
#include "store.h"
#include "printf.h"
#include "uartPort.h"
#include "SEGGER_RTT.h"

#define DATA_FRAME_INFO_SIZE 10
#define DATA_FRAME_USED_FLAG 0x12aa
#define DATA_FRAME_USED_FLAG_POS 0
#define DATA_FRAME_USED_LEN_POS 2
#define DATA_FRAME_PORT_POS 4
#define DATA_FRAME_STATUS_POS 5
#define DATA_FRAME_TIMESTAMP_POS 6

osMessageQueueId_t dataSaveQueue;
osThreadId_t dataSaveTask;
void dataSaveTaskFunc(void *argument);

void dataProcess_init(){

    //创建存储队列，长度稍微大于数据池大小，用于空闲中断中追加标记上一帧数据已经完成的特殊帧
    dataSaveQueue = osMessageQueueNew(DATA_POOL_NUM*1.3, sizeof(DataFrame), NULL);

    //创建存储任务
    const osThreadAttr_t dataSaveTaskAttr = {
        .name = "dataSaveTask",
        .stack_size = 128 * 4,
        .priority = (osPriority_t)osPriorityNormal,
    };
    dataSaveTask = osThreadNew(dataSaveTaskFunc, NULL, &dataSaveTaskAttr);
    if(dataSaveTask==NULL) SEGGER_RTT_WriteString(0,"dataSaveTaskHandleError\r\n");
}
/**
 * @brief 获取一个数据区，用于存放数据
 * 
 * @param frame 
 * @param port 
 * @return uint8_t 0:成功 1:失败
 * 
 * @note 数据的生产者通过这个函数获取一个数据区，数据区的可用长度放在frame->total_len中
 * 生产者可以使用DMA等方式将数据直接写入这个数据区，在记下数据的实际长度后，更新frame->used_len
 * 在数据生产完成后，调用data_finish函数，如果一帧数据已经完成，frame->status置0，否则置1
 */
uint8_t getDataArea(DataFrame* frame, uint8_t port){
    frame->p = dataPool_get(0);
    if(frame->p == NULL) return 1;
    frame->p +=DATA_FRAME_INFO_SIZE;
    frame->total_len = DATA_POOL_SIZE-DATA_FRAME_INFO_SIZE;
    frame->used_len = 0;
    frame->port = port;
    return 0;
}

void data_finish(DataFrame* frame,uint16_t used_len,uint8_t status){
    frame->timestamp = osKernelGetTickCount();
    frame->used_len = used_len;
    frame->status = status;
    frame->p -= DATA_FRAME_INFO_SIZE;
    *(uint16_t*)frame->p = DATA_FRAME_USED_FLAG;
    *(uint16_t*)(&frame->p[DATA_FRAME_USED_LEN_POS]) = frame->used_len;
    frame->p[DATA_FRAME_PORT_POS]=frame->port;
    frame->p[DATA_FRAME_STATUS_POS]=frame->status;
    *(uint32_t*)(&frame->p[DATA_FRAME_TIMESTAMP_POS])=frame->timestamp;
    //放入存储队列，freeRTOS是通过复制数据的方式放入队列的
    osMessageQueuePut(dataSaveQueue, frame, 0, 0);
}
/**
 * @brief 用于空闲中断中追加标记上一帧数据已经完成
 * 
 * @param port 
 */
void data_appendFinishFlag(uint8_t port){
    //特殊帧，表示追加完成标志
    DataFrame frame={.p=NULL,.total_len=1,.used_len=0,.port=port,.status=0,.timestamp=0};
    osMessageQueuePut(dataSaveQueue, &frame, 0, 0);
}
/**
 * @brief 用于抛弃一个数据包
 * 
 * @param frame 
 */
void data_destroy(DataFrame* frame){
    if (frame == NULL || frame->p == NULL) return;
    frame->p -= DATA_FRAME_INFO_SIZE;
    dataPool_free(frame->p);
}


/*-----------------消费者(缓冲区输出)-------------------*/
void flash_appendFinishFlag(uint32_t log_addr, uint8_t port) {
  uint32_t tmp_addr = log_addr - DATA_POOL_SIZE;
  uint8_t buf[DATA_FRAME_INFO_SIZE];
  for (; tmp_addr > LOG_DATA_ADDR; tmp_addr -= DATA_POOL_SIZE) {
    norflash_read(buf, tmp_addr, DATA_FRAME_INFO_SIZE);
    // 如果端口一样，更新状态位
    if (buf[DATA_FRAME_PORT_POS] == port) {
      uint8_t tmp = 0;
      norflash_write_nocheck(&tmp, tmp_addr + DATA_FRAME_STATUS_POS, 1);
      break;
    }
  }
}
void dataSaveTaskFunc(void *argument){
    DataFrame frame;
    DataFrame frame2;
    uint32_t log_addr = LOG_DATA_ADDR;
    //首次运行擦除一个扇区
    norflash_erase_sector(log_addr/SECTOR_SIZE);
    while(1){
        if(osMessageQueueGet(dataSaveQueue, &frame, NULL, osWaitForever) == osOK){
            if(xQueuePeek(dataSaveQueue, &frame2, 0)==pdTRUE){
                if(frame2.total_len == 1 && frame2.status == 0&&frame2.used_len == 0){
                    //下一个是追加完成标志，合并
                    if(frame2.port == frame.port){
                        //把队列的数据拿走
                        osMessageQueueGet(dataSaveQueue, &frame2, NULL, 0);
                        frame.status = 0;
                        frame.p[DATA_FRAME_STATUS_POS] = 0;
                    }
                    else{
                        osMutexAcquire(w25q64Mutex,osWaitForever);
                        //端口不一样，不能合并,回溯写入
                        flash_appendFinishFlag(log_addr, frame.port);
                        osMutexRelease(w25q64Mutex);
                    }
                }
            }
            else{
                if(frame.total_len == 1 && frame.status == 0&&frame.used_len == 0){
                    //没有下一个了，且是追加完成标志，回溯写入
                    osMutexAcquire(w25q64Mutex,osWaitForever);
                    flash_appendFinishFlag(log_addr, frame.port);
                    osMutexRelease(w25q64Mutex);
                    continue;
                }
            }
            osMutexAcquire(w25q64Mutex,osWaitForever);
            //如果下一页数据是在新的扇区，要先把标识符清掉
            if((log_addr+DATA_POOL_SIZE) %SECTOR_SIZE == 0){
              //下一个要写的地址是新的扇区，先擦除，避免旧的数据依然存在，但是擦除前先把数据帧的标识符写掉，防止突然断电
              //问题是突然断电时，还没来得及擦除扇区，这个方法是先写入数据0，把标识符去掉，但是只是让问题发生的概率变小，并不能完全避免问题
                uint16_t tmp = 0;
                norflash_write_nocheck((uint8_t*)(&tmp), log_addr+DATA_POOL_SIZE, 2);
            }
            //写入存储
            if(*(uint16_t*)frame.p == DATA_FRAME_USED_FLAG){//感觉这个if是没有用的
                // norflash_write(frame.p, log_addr, DATA_POOL_SIZE);
                norflash_write_nocheck(frame.p, log_addr, DATA_POOL_SIZE);
                log_addr += DATA_POOL_SIZE;
            }
            osMutexRelease(w25q64Mutex);
            //释放数据区
            dataPool_free(frame.p);
            if(log_addr %SECTOR_SIZE == 0){
              osMutexAcquire(w25q64Mutex,osWaitForever);
              /* 扇区擦除 */
              norflash_erase_sector(log_addr / SECTOR_SIZE);
              osMutexRelease(w25q64Mutex);
            }
        }
    }
}

/**
 * @brief 读取数据帧的包头信息
 * 
 * @param frame 
 * @param addr 
 * @return uint8_t 0:成功 1:不存在数据 2:地址非法
 * @warning 调用此函数时，要确保不能正在写入
 */
uint8_t dataReadHead(DataFrame* frame, uint32_t addr){
    //地址不在数据记录的区域
    if(addr < LOG_DATA_ADDR) return 2;
    //地址不是页的整数倍
    if((addr-LOG_DATA_ADDR)%DATA_POOL_SIZE != 0) return 2;
    //先读出包头信息部
    uint8_t buf[DATA_FRAME_INFO_SIZE];
    norflash_read(buf, addr, DATA_FRAME_INFO_SIZE);
    //判断是否存在日志数据
    if(*(uint16_t*)buf != DATA_FRAME_USED_FLAG) return 1;
    //包信息赋值
    frame->used_len = *(uint16_t*)(&buf[DATA_FRAME_USED_LEN_POS]);
    frame->port = buf[DATA_FRAME_PORT_POS];
    frame->status = buf[DATA_FRAME_STATUS_POS];
    frame->timestamp=*(uint32_t*)(&buf[DATA_FRAME_TIMESTAMP_POS]);
    return 0;
}

/**
 * @brief 读取数据帧
 * 
 * @param frame 
 * @param addr 
 * @return uint8_t 0:成功 1:不存在数据 2:地址非法
 * @warning 调用此函数时，要确保不能正在写入
 */
uint8_t dataRead(DataFrame* frame, uint32_t addr){
    //读出包头
    uint8_t ret = dataReadHead(frame, addr);
    if(ret != 0) return ret;
    //读出包数据
    norflash_read(frame->p, addr+DATA_FRAME_INFO_SIZE, frame->used_len);
    return 0;
}
/**
 * @brief 通过索引查找数据地址
 * 
 * @param port 
 * @param index 从0开始
 * @param addr 
 * @return uint8_t 0:成功 1:index过大，找不到了 2:地址非法 3:参数错误
 * @note 如果返回1，addr是最后该端口的地址
 */
uint8_t dataFindByIndex(uint8_t port,uint32_t index,uint32_t * addr){
    if (addr == NULL) return 3;
    if(port == 0 || port > PORT_NUM) return 3;
    uint32_t log_addr = LOG_DATA_ADDR;//后续要改成从索引表中查找，todo
    uint32_t found = 0;
    DataFrame frame;
    while(1){
        uint8_t ret = dataReadHead(&frame, log_addr);
        if(ret == 0){
            if(frame.port == port){
                if(index == 0){
                    *addr = log_addr;
                    return 0;
                }
                if(frame.status == 0){
                    index--;
                }
                found =  log_addr;
            }
            log_addr += DATA_POOL_SIZE;
        }
        else if(ret == 1){
            //不存在数据，说明已经所有日志已经结束
            *addr = found;
            return 1;
        }
        else if(ret == 2){
            //地址错误，读取的区域不是日志的区域
            return 2;
        }
        else{
            return 2;
        }
    }
}
/**
 * @brief 输出数据
 * 
 * @param port 端口号，从1开始
 * @param index 从0开始，表示从第index+1个数据块开始输出
 * @param nums 从index开始输出nums个数据块，UINT32_MAX表示全部输出，0表示不输出
 * @param realNums 实际输出的数据块数量，可以为NULL
 * @param cortolFlag 位图，bit0:输出时间戳 bit1:输出序号
 * @param output_str 
 * @return uint8_t 0:成功 1:超时 2:index过大 3:未知
 * @todo 加入索引表，加快索引效率
 */
uint8_t frameStrOut(uint8_t port, uint32_t index, uint32_t nums,uint32_t* realNums,
                    uint8_t cortolFlag,uint32_t waitTime,
                    void output_str(const char* str, uint16_t len)) 
{
    if(realNums != NULL) *realNums = 0;
    if(port == 0 || port > PORT_NUM) return 3;
    if(nums == 0){
        return 0;
    }
    DataFrame frame;
    //解析控制位
    uint8_t timeEnable = cortolFlag & 0x01;
    uint8_t seqEnable = (cortolFlag >> 1) & 0x01;

    //通过索引查找数据地址
    uint32_t startAddr;
    uint8_t ret_ = dataFindByIndex(port, index, &startAddr);
    if(ret_ == 1){
        if(realNums != NULL) *realNums = 0;
        return 2;
    }
    else if(ret_ == 2){
        if(realNums != NULL) *realNums = 0;
        return 3;
    }
    else if(ret_ != 0){
        if(realNums != NULL) *realNums = 0;
        return 3;
    }

    //获取资源
    frame.p = dataPool_get(waitTime);
    if(frame.p==NULL){
        if(realNums != NULL) *realNums = 0;
        return 1;
    }
    osStatus_t ret = osMutexAcquire(w25q64Mutex,waitTime);
    if(ret == osErrorTimeout){
        return 1;
    }

    char str[16];
    int len;
    uint8_t res = 0;
    uint8_t frameReady = 1;//0表示一帧数据还没输出完，正在输出中，1表示一帧数据已经输出完，准备输出下一帧数据
    for(uint32_t i = 0;i<nums;startAddr+=DATA_POOL_SIZE){
      uint8_t readRet = dataRead(&frame, startAddr);
      if (readRet == 0) {  // 正常收到数据
        if(frame.port!=port){//如果数据不是该端口的，则跳过
            continue;
        }
        //输出序号
        if(seqEnable == 1 && frameReady == 1){
            len = sprintf(str,"%lu:\r\n",i+1);
            output_str(str,len);
        }
        //输出数据帧来的时候的时间戳
        if(timeEnable == 1 && frameReady == 1){
            len = sprintf(str,"[%lu]\r\n",frame.timestamp);
            output_str(str,len);
        }
        //输出数据
        output_str((char*)frame.p,frame.used_len);

        //如果一帧数据已经完成，输出两个换行
        if(frame.status==0){
            //输出数据帧结束时的时间戳，并标记下一条数据输出时需要时间戳
            //如果frameReady等于0,加上status等于0，说明此帧数据只有一个数据包，不用输出结束时间戳
            if(timeEnable == 1 && frameReady == 0){
                len = sprintf(str,"\r\n[%lu]",frame.timestamp);
                output_str(str,len);
            }
            output_str("\r\n\r\n",4);
            i++;
            frameReady = 1;
            if(realNums != NULL) *realNums = i;
        }
        else{
            frameReady = 0;
        }
      } else if (readRet == 1) {  // 不存在数据，说明已经所有日志已经结束
        if (nums != UINT32_MAX) res = 2;
        else res = 0;
        break;
      } else if (readRet == 2) {  // 地址错误，读取的区域不是日志的区域
        res = 3;
        break;
      }
    }
    dataPool_free(frame.p);
    osMutexRelease(w25q64Mutex);
    return res;
}